package gateway.service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSONObject;
import com.yymt.comp.base.utils.IPUtils;
import com.yymt.comp.gateway.constant.Constants;
import com.yymt.comp.gateway.entity.RouteInstance;
import com.yymt.comp.gateway.entity.ServerInstance;
import com.yymt.comp.gateway.event.GatewayAccessEvent;
import com.yymt.comp.gateway.utils.HttpRequestMapper;
import com.yymt.comp.gateway.utils.HttpResponseMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@Service
public class RoutingDelegateService {

  private HttpResponseMapper responseMapper;

  private HttpRequestMapper requestMapper;

  @Resource
  private RestTemplate restTemplate;

  @Resource
  private ApplicationEventPublisher applicationEventPublisher;

  /**
   * 根据相应策略转发请求到对应后端服务
   *
   * @param ab RouteInstance
   * @param request  HttpServletRequest
   * @param response HttpServletResponse
   */
  public void doForward(RouteInstance ab, HttpServletRequest request, HttpServletResponse response) {
    RouteInstance tmpRoute = new RouteInstance();

    tmpRoute.setUri(ab.getUri());
    tmpRoute.setPath(ab.getPath());
    tmpRoute.setRewrite(ab.isRewrite());

    boolean shouldLB = StringUtils.startsWith(tmpRoute.getUri(), Constants.LB_PREFIX);
    // TODO 可以增加负载均衡相关逻辑
    if (shouldLB) {
      // 需要负载均衡,获取appName
      String appName = StringUtils.substringAfter(tmpRoute.getUri(), Constants.LB_PREFIX);
      //从请求头中获取是否必须按user去路由到同一节点
      // 可用节点
      ServerInstance chooseInstance = chooseLBInstance(appName);
      //设置route instance uri 为负载均衡之后的URI地址
      String uri = Constants.HTTP_PREFIX + chooseInstance.getHost() + ":" + chooseInstance.getPort();
      tmpRoute.setUri(uri);
    }
    // 转发请求
    try {
      goForward(request, response, tmpRoute);
    }catch (HttpClientErrorException ex){
      renderError(response, ex);
    } catch (Exception e) {
      // 连接超时、返回异常
      log.error("request error {}", e.getMessage(),e);
      JSONObject result = new JSONObject();
      result.put("code", 500);
      result.put("msg", e.getMessage());
      renderString(response, result.toJSONString());
    }

  }

  /**
   * 发送请求到对应后端服务
   *
   * @param request  HttpServletRequest
   * @param response HttpServletResponse
   * @param instance RouteInstance
   * @throws IOException io读写异常
   */
  private void goForward(HttpServletRequest request, HttpServletResponse response, RouteInstance instance) throws IOException {
    TimeInterval timer = DateUtil.timer();
    requestMapper = new HttpRequestMapper();
    // 构建请求对象
    RequestEntity<byte[]> requestEntity = requestMapper.map(request, instance);
    //用byte数组处理返回结果，因为返回结果可能是字符串也可能是数据流
    ResponseEntity<byte[]> responseEntity = restTemplate.exchange(requestEntity, byte[].class);
    responseMapper = new HttpResponseMapper();
    responseMapper.map(responseEntity, response);
    // 发布网关转发事件
    long interval = timer.interval();
    Object userInfo = request.getAttribute("userInfo");
    if (ObjectUtil.isNotEmpty(userInfo)) {
      GatewayAccessEvent event = new GatewayAccessEvent(this, requestEntity.getUrl().getPath(),
              IPUtils.getIpAddr(request), request.getMethod(), userInfo);
      event.setTime(interval);
      applicationEventPublisher.publishEvent(event);
    }
  }

  private ServerInstance chooseLBInstance(String appName) {
    //TODO 根据appName 选择对应的host
    ServerInstance instance = new ServerInstance();
    instance.setHost("127.0.0.1");
    instance.setPort(10000);
    return instance;
  }

  /**
   * 写回字符串结果到客户端
   *
   * @param response 响应对象
   * @param string 待渲染的字符串数据
   */
  public void renderString(HttpServletResponse response, String string) {
    try {
      response.setStatus(200);
      response.setContentType("application/json");
      response.setCharacterEncoding("utf-8");
      response.getWriter().print(string);
    } catch (IOException e) {
      log.info("数据渲染异常",e);
    }
  }

  public void renderError(HttpServletResponse response, HttpClientErrorException exception) {
    try {
      response.setStatus(exception.getRawStatusCode());
      response.getOutputStream().write(exception.getResponseBodyAsByteArray());
    } catch (IOException e) {
      log.info("数据渲染异常",e);
    }
  }
}
